﻿using System;
using FluentParser.Expressions;
using FluentParser.Expressions.Rules.Composition;
using FluentParser.Expressions.Rules.Decoration;
using FluentParser.Utils;

namespace FluentParser
{
    public static class RulesFactory
    {
        public static Symbol Optional(this Symbol source)
        {
            return new OptionalSymbolRule(source, String.Empty);
        }

        public static Symbol Optional(this Symbol source, string defaultValue)
        {
            return new OptionalSymbolRule(source, defaultValue);
        }

        public static Expr<TS> Optional<TS>(this Expr<TS> source)
        {
            return new OptionalExpressionRule<TS>(source, default(TS));
        }

        public static Expr<TS> Optional<TS>(this Expr<TS> source, TS defaultValue)
        {
            return new OptionalExpressionRule<TS>(source, defaultValue);
        }

        public static Expr<TS[]> Repeat<TS>(this Expr<TS> source, int minimumCount)
        {
            return new RepeatExpressionRule<TS>(source, minimumCount, null);
        }

        public static Expr<TS[]> Repeat<TS>(this Expr<TS> source, int minimumCount, Symbol separator)
        {
            return new RepeatExpressionRule<TS>(source, minimumCount, separator);
        }

        public static Expr<TR> Convert<TS1, TS2, TR>(this Expr<Tuple<TS1, TS2>> source, Func<TS1, TS2, TR> predicate)
        {
            return new ConvertExpressionRule<Tuple<TS1, TS2>, TR>(source, x => predicate(x.Item1, x.Item2));
        }

        public static Expr<TR> Convert<TS1, TS2, TS3, TR>(this Expr<Tuple<TS1, TS2, TS3>> source, Func<TS1, TS2, TS3, TR> predicate)
        {
            return new ConvertExpressionRule<Tuple<TS1, TS2, TS3>, TR>(source, x => predicate(x.Item1, x.Item2, x.Item3));
        }

        public static Expr<TR> Convert<TS1, TS2, TS3, TS4, TR>(this Expr<Tuple<TS1, TS2, TS3, TS4>> source, Func<TS1, TS2, TS3, TS4, TR> predicate)
        {
            return new ConvertExpressionRule<Tuple<TS1, TS2, TS3, TS4>, TR>(source, x => predicate(x.Item1, x.Item2, x.Item3, x.Item4));
        }

        public static Symbol Surround(this Symbol source, string delimiter)
        {
            return new SurroundSymbolRule(delimiter, source, delimiter);
        }

        public static Symbol Surround(this Symbol source, Symbol delimiter)
        {
            return new SurroundSymbolRule(delimiter, source, delimiter);
        }

        public static Expr<TS> Surround<TS>(this Expr<TS> source, string delimiter)
        {
            return new SurroundExpressionRule<TS>(delimiter, source, delimiter);
        }

        public static Expr<TS> Surround<TS>(this Expr<TS> source, Symbol delimiter)
        {
            return new SurroundExpressionRule<TS>(delimiter, source, delimiter);
        }

        public static Symbol Surround(this Symbol source, string leftDelimiter, string rightDelimiter)
        {
            return new SurroundSymbolRule(leftDelimiter, source, rightDelimiter);
        }

        public static Symbol Surround(this Symbol source, Symbol leftDelimiter, Symbol rightDelimiter)
        {
            return new SurroundSymbolRule(leftDelimiter, source, rightDelimiter);
        }

        public static Expr<TS> Surround<TS>(this Expr<TS> source, string leftDelimiter, string rightDelimiter)
            
        {
            return new SurroundExpressionRule<TS>(leftDelimiter, source, rightDelimiter);
        }

        public static Expr<TS> Surround<TS>(this Expr<TS> source, Symbol leftDelimiter, Symbol rightDelimiter)
        {
            return new SurroundExpressionRule<TS>(leftDelimiter, source, rightDelimiter);
        }

        public static Symbol Join(this Symbol left, Symbol right)
        {
            return new GroupSymbolRule(left, right);
        }

        public static Symbol Join(this Symbol left, string right)
        {
            return new GroupSymbolRule(left, right);
        }

        public static Expr<TR> Join<TR>(this Symbol left, Expr<TR> right)
        {
            return new GroupExpressionRule<object, TR, TR>(left, right, (x, y) => y);
        }

        public static Expr<TL> Join<TL>(this Expr<TL> left, string right)
        {
            return new GroupExpressionRule<TL, object, TL>(left, (Symbol)right, (x, y) => x);
        }

        public static Expr<TL> Join<TL>(this Expr<TL> left, Symbol right)
        {
            return new GroupExpressionRule<TL, object, TL>(left, right, (x, y) => x);
        }

        public static Expr<Tuple<TL, TR>> Join<TL, TR>(this Expr<TL> left, Expr<TR> right)
        {
            return new GroupExpressionRule<TL, TR, Tuple<TL, TR>>(left, right, Tuple.Create);
        }

        public static Expr<Tuple<TL, TR1, TR2>> Join<TL, TR1, TR2>(this Expr<TL> left, Expr<Tuple<TR1, TR2>> right)
        {
            return new GroupExpressionRule<TL, Tuple<TR1, TR2>, Tuple<TL, TR1, TR2>>(left, right, Tuple.Combine);
        }

        public static Expr<Tuple<TL, TR1, TR2, TR3>> Join<TL, TR1, TR2, TR3>(this Expr<TL> left, Expr<Tuple<TR1, TR2, TR3>> right)
        {
            return new GroupExpressionRule<TL, Tuple<TR1, TR2, TR3>, Tuple<TL, TR1, TR2, TR3>>(left, right, Tuple.Combine);
        }

        public static Expr<Tuple<TL1, TL2, TR>> Join<TL1, TL2, TR>(this Expr<Tuple<TL1, TL2>> left, Expr<TR> right)
        {
            return new GroupExpressionRule<Tuple<TL1, TL2>, TR, Tuple<TL1, TL2, TR>>(left, right, Tuple.Combine);
        }

        public static Expr<Tuple<TL1, TL2, TR1, TR2>> Join<TL1, TL2, TR1, TR2>(this Expr<Tuple<TL1, TL2>> left, Expr<Tuple<TR1, TR2>> right)
        {
            return new GroupExpressionRule<Tuple<TL1, TL2>, Tuple<TR1, TR2>, Tuple<TL1, TL2, TR1, TR2>>(left, right, Tuple.Combine);
        }

        public static Expr<Tuple<TL1, TL2, TL3, TR>> Join<TL1, TL2, TL3, TR>(this Expr<Tuple<TL1, TL2, TL3>> left, Expr<TR> right)
        {
            return new GroupExpressionRule<Tuple<TL1, TL2, TL3>, TR, Tuple<TL1, TL2, TL3, TR>>(left, right, Tuple.Combine);
        }

        public static Expr<TL> Extends<TL, TR>(this Expr<TL> left, Expr<TR> right, Func<TL, TR, TL> reduce)
        {
            throw new NotImplementedException();
        }

        public static Expr<TS> Where<TS>(this Expr<TS> source, Func<TS, bool> predicate)
        {
            return new ConditionExpressionRule<TS>(source, predicate);
        }

        public static Expr<Tuple<TS1, TS2>> Where<TS1, TS2>(this Expr<Tuple<TS1, TS2>> source, Func<TS1, TS2, bool> predicate)
        {
            return new ConditionExpressionRule<Tuple<TS1, TS2>>(source, x => predicate(x.Item1, x.Item2));
        }

        public static Expr<Tuple<TS1, TS2, TS3>> Where<TS1, TS2, TS3>(this Expr<Tuple<TS1, TS2, TS3>> source, Func<TS1, TS2, TS3, bool> predicate)
        {
            return new ConditionExpressionRule<Tuple<TS1, TS2, TS3>>(source, x => predicate(x.Item1, x.Item2, x.Item3));
        }

        public static Expr<Tuple<TS1, TS2, TS3, TS4>> Where<TS1, TS2, TS3, TS4>(this Expr<Tuple<TS1, TS2, TS3, TS4>> source, Func<TS1, TS2, TS3, TS4, bool> predicate)
        {
            return new ConditionExpressionRule<Tuple<TS1, TS2, TS3, TS4>>(source, x => predicate(x.Item1, x.Item2, x.Item3, x.Item4));
        }

        public static Symbol Where(this Symbol symbol, Func<object, bool> predicate)
        {
            return new ConditionSymbolRule(symbol, predicate);
        }

        /// <summary>
        /// Decorates a symbol by transforming it into a symbol.
        /// This allows to remove it's resulting value.
        /// </summary>
        /// <returns>Decorated expression</returns>
        public static Symbol ToSymbol<T>(this Expr<T> source)
        {
            return new CastToSymbolRule<T>(source);
        }

        /// <summary>
        /// Decorate a symbol returning no value with an expression
        /// returning a value of type string.
        /// </summary>
        /// <returns>Decorated symbol</returns>
        public static Expr<object> ToExpr(this Symbol source)
        {
            return new CastToExpressionRule<object, object>(source);
        }
    }
}